package com.vains.config.security;

import com.vains.filter.JWTAuthenticationFilter;
import com.vains.filter.JWTAuthorizationFilter;
import com.vains.service.impl.CustomUserDetailsService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.IOException;

/**
 * web安全配置
 *
 * @author vains
 * @date 2020/10/21 15:57
 */
@Slf4j
@Configuration
@EnableWebSecurity
@AllArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final PasswordEncoder passwordEncoder;

    private final CustomUserDetailsService customUserDetailsService;

    // 不需要认证的URL
    private static final String[] PERMIT_URL = {"/test/login"};

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests(authorize -> authorize
                .antMatchers(PERMIT_URL).permitAll()
                .anyRequest().authenticated()
            ).exceptionHandling()
             // 无权限回调
            .accessDeniedHandler(this::accessDeniedHandler)
             // 未登录回调
            .authenticationEntryPoint(this::authenticationEntryPoint)
             // 登录过滤器和解析token的过滤器，添加到Security的过滤器链中
            .and().addFilter(new JWTAuthenticationFilter(authenticationManagerBean()))
            .addFilter(new JWTAuthorizationFilter(authenticationManagerBean()))
             // 禁用session
            .csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 注入自定义的UserDetailsService和密码解析器
        auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder);
    }

    /**
     * 未登录处理
     * @param request 请求对象
     * @param response 响应对象
     * @param e 异常
     */
    private void authenticationEntryPoint(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
        log.warn("未登录异常信息：{}", e.getMessage());
        try {
            response.setCharacterEncoding("UTF-8");
            if (e instanceof BadCredentialsException) {
                response.getWriter().println(e.getMessage());
                return;
            }
            response.getWriter().println("invoke authenticationEntryPoint");
        } catch (IOException ex) {
            log.error("响应错误", ex);
        }
    }

    /**
     * 无权限处理
     * @param request 请求对象
     * @param response 响应对象
     * @param e 异常
     */
    private void accessDeniedHandler(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) {
        log.warn("权限不足异常信息：{}", e.getMessage());
        try {
            response.getWriter().println("invoke accessDeniedHandler");
        } catch (IOException ex) {
            log.error("响应错误", ex);
        }
    }

    /**
     * 不在这里注入就无法在别的地方使用 AuthenticationManager
     *  调用Security认证所需的类
     * @return AuthenticationManager
     * @throws Exception Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

}
